/**
 * 20220117：自原hardLine中分离
 * 20220426:扩充贴图相关逻辑
 * 20220507:修改贴图定义的书写错误
 */
import {ShaderMaterial, Color} from "three";

/**
 * 硬角厚度线材质
 * @param values {object} 可选参数对象
 *      {
 *           color ：new Color(0, 1, 0); {Color} 颜色
 *           width ： 0.5; {number} 线宽
 *           dashSize ： 0.1; {number} 虚线段长（应与几何体保持一致）
 *           gapSize ： 0.1; {number} 虚线段间隔长（应与几何体保持一致）
 *           perspectiveFactor ： 2; {number} 透视系数（影响线宽）
 *           dashFill ： true; {boolean} 填充虚线间隔（true为实线，false为虚线）
 *           sizeAttenuation ： true; {boolean} 保持视觉大小
 *           zOffset ： 0; {number} 深度偏移
 *           map:null, {texture} 贴图
 *      }
 */
export default class HardLineMaterial extends ShaderMaterial{
    constructor(values={}){
        super();
        this._init(values);
    }

    //.................................................public.......................................................

    get color(){
        return this._color;
    }
    set color(color){
        this.uniforms.color.value=this._color.copy(color);
    }

    get width(){
        return this._width;
    }
    set width(num){
        this.uniforms.width.value=this._width=num;
    }

    get dashSize(){
        return this._dashSize;
    }
    set dashSize(num){
        this.uniforms.dashSize.value=this._dashSize=num;
    }

    get gapSize(){
        return this._gapSize;
    }
    set gapSize(num){
        this.uniforms.gapSize.value=this._gapSize=num;
    }

    get perspectiveFactor(){
        return this._perspectiveFactor;
    }
    set perspectiveFactor(num){
        this.uniforms.perspectiveFactor.value=this._perspectiveFactor=num;
    }

    get dashFill(){
        return this._dashFill;
    }
    set dashFill(bol){
        this.uniforms.dashFill.value=this._dashFill=bol;
    }

    get sizeAttenuation(){
        return this._sizeAttenuation;
    }
    set sizeAttenuation(bol){
        this.uniforms.sizeAttenuation.value=this._sizeAttenuation=bol;
    }

    get zOffset(){
        return this._zOffset;
    }
    set zOffset(num){
        this.uniforms.zOffset.value=this._zOffset=num;
    }
    
    //20220426
    get map(){
        return this._map;
    }
    set map(texture){

       //20220507
       // this.defines['USE_MAP']=texture?'':undefined;   
       this._map?(this.defines['USE_MAP']=''):(delete this.defines['USE_MAP']);

       this.uniforms.map.value=this._map=texture||null;
       this.needsUpdate=true;
    }

    //.....................................................private...................................................

    _init(values){
        this._initOriParas(values);
        this._initVariable(values);
        this._initVertexShader();
        this._initFragmentShader();
        this._initUniforms();
    }
    _getExclusiveKeys(){
        return ['width','color','dashSize','gapSize','perspectiveFactor','dashFill','sizeAttenuation','zOffset','map'];
    }
    _initOriParas(values){
        let ecl=this._getExclusiveKeys();
        for(let k in values){
            let key=k+'';
            if(ecl.includes(key))continue;
            this[key]=values[key];
        }
    }
    _initVariable(values) {
        this._color = values.color || new Color(0, 1, 0);
        this._width = values.width || 0.5;
        this._dashSize = values.dashSize || 0.1;
        this._gapSize =  values.gapSize || 0.1;
        this._perspectiveFactor = values.perspectiveFactor || 2;
        this.opacity = values.hasOwnProperty('opacity') ? values.opacity : 1;
        this._dashFill = values.hasOwnProperty('dashFill') ? values.dashFill : true;
        this._sizeAttenuation = values.hasOwnProperty('sizeAttenuation') ? values.sizeAttenuation : true;
        this._zOffset = values.hasOwnProperty('zOffset') ? values.zOffset : 0;
        //20220426
        this._map=values.map||null;
    }
    _initVertexShader(){
        this.vertexShader=`
            attribute vec3 nextPosition;
            attribute float offset;
            attribute float lineDistance;
            attribute float dashMark;
            uniform float width;
            uniform float perspectiveFactor;
            uniform bool sizeAttenuation;
            uniform float zOffset;
            varying float vDistance;
            varying float vDashMark;
            varying vec2 vuv;
            float computeAspect(){ 
               float aspect=0.5625;
               if(projectionMatrix[3][3]==0.0){ 
                  float _tan=projectionMatrix[1][1];
                  float as_tan=1.0/projectionMatrix[0][0];
                  aspect=1.0/(as_tan*_tan);
               }else{ 
                  float _height=1.0/projectionMatrix[1][1];
                  float _width=1.0/projectionMatrix[0][0];
                  aspect=_height/_width;
               }
               return aspect;
            }
            void main(){
               vec4 mvPosition=modelViewMatrix*vec4(position,1.0);
               vec4 pmvPosition=projectionMatrix*mvPosition;
               vec4 mvNextPosition=modelViewMatrix*vec4(nextPosition,1.0);
               vec4 nxtP=projectionMatrix*mvNextPosition;
               vec4 curP=pmvPosition;
               vec3 nxt=vec3(nxtP.xyz/nxtP.w);
               vec3 cur=vec3(curP.xyz/curP.w);
               vec3 dirOri=nxt-cur;
               vec3 look=vec3(0.0,0.0,1.0);

               vec3 dirCC=mvPosition.xyz/mvPosition.w;
               vec3 dirCN=mvNextPosition.xyz/mvNextPosition.w;
               float dotC=dot(look,dirCC);
               float dotN=dot(look,dirCN);
               if(dotC*dotN<0.){ 
                  if(projectionMatrix[3][3]==0.0){ 
                       look=vec3(0.,0.,-1.);
                   }
               }

               float aspect=computeAspect();
               dirOri.y*=aspect;
               vec3 crs=cross(look,dirOri);
               vec3 dir=normalize(crs);
               dir.x*=aspect;
               float rad=width;
               pmvPosition.z+=zOffset;
               if(sizeAttenuation){ 
                   rad*=projectionMatrix[1][1];
                  gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset,pmvPosition.w);
               }else{ 
                  gl_Position=vec4(vec3(pmvPosition.xyz)+dir*rad/perspectiveFactor*offset*pmvPosition.w,pmvPosition.w);
               }
               vDistance=lineDistance;
               vDashMark=dashMark;
               vuv=vec2(lineDistance,sign(offset)*0.5);
            }
        `;
    }

    _initFragmentShader(){
        //20220426
        this.fragmentShader=`
            uniform float dashSize; 
            uniform float gapSize;
            uniform float opacity; 
            uniform bool dashFill;
            uniform vec3 color;
            #ifdef USE_MAP
                uniform sampler2D map;
            #endif
            varying float vDistance;
            varying float vDashMark;
            varying vec2 vuv;
            void main(){
                vec4 tsl=vec4(1.);
                #ifdef USE_MAP
                    tsl=texture(map,vec2(vuv.x,vuv.y+0.5));
                #endif
                vec4 col=vec4(color.r*tsl.r,color.g*tsl.g,color.b*tsl.b,tsl.a*opacity);
                if(dashFill||vDashMark==0.){
                    gl_FragColor=col;
                }else{
                    float dev=mod(vDistance,dashSize+gapSize);
                    if(dev>dashSize){
                        discard;
                    }
                    gl_FragColor=col;
               }
            }
        `;
    }
    
    _initUniforms(){
        this.uniforms={
            color:{value:this._color},
            width:{value:this._width},
            dashSize:{value:this._dashSize},
            perspectiveFactor:{value:this._perspectiveFactor},
            gapSize:{value:this._gapSize},
            opacity:{value:this.opacity},
            dashFill:{value:this._dashFill},
            sizeAttenuation:{value:this._sizeAttenuation},
            zOffset:{value:this._zOffset},
            
            //20220426
            map:{value:this._map},
        };

        //20220507
        //this.defines['USE_MAP']=texture?'':undefined;  20220507
        this._map?(this.defines['USE_MAP']=''):(delete this.defines['USE_MAP']);

        this.needsUpdate=true;
    }
}